package chat.server.service;

import chat.server.view.ServerView;
import chat.tools.MsgTypeEnum;
import chat.tools.ServiceTools;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;

/**
 * 服务器服务功能类
 *
 * #1 开启服务，等待客户端连接。
 *
 * @author 谈笑、
 * @dateTime 2020/10/22 9:19
 */
public class ServerCore implements Runnable {

	// 服务端窗体
	private ServerView serverView;


	// 创建服务套接字
	private ServerSocket serverSocket;


	// 服务器运行状态
	private boolean isServerCoreRun = false;


	// 客户端容器，存储成功连接到服务器的客户端。
	private Map<String, NewClient> allClient = new HashMap<>();


	public ServerCore() {}


	// 构造函数初始化类中的成员属性
	public ServerCore(ServerView serverView) {
		this.serverView = serverView;
	}


	/**
	 * 启动服务器
	 *
	 * #1 启动服务器，并设置服务器的IP地址和端口号。
	 */
	public boolean startServer(String ip, int port) {
		try {
			// 实体化服务器套接字
			this.serverSocket = new ServerSocket();

			// 将服务器套接字绑定到指定的IP地址和端口
			this.serverSocket.bind(new InetSocketAddress(ip, port));

			// 启动服务器成功之后才能运行服务器
			this.isServerCoreRun = true;

			// 服务器启动成功
			return true;

		} catch (IOException e) {
			e.printStackTrace();
			// 服务器启动失败，一般是IP地址或端口异常。
			return false;
		}
	}


	/**
	 * 设置服务器运行状态
	 *
	 * #1 停止服务器运行，客户端即无法连接。
	 */
	public synchronized  void setServerCoreRun() {
		this.isServerCoreRun = false;
	}


	/**
	 * 关闭服务
	 *
	 * #1 断开与所有客户端的连接，并将客户端容器中的所有已连接的客户端清空。
	 * #2 关闭服务器套接字
	 */
	public void closeServer() {
		try {

			// 停止服务器运行
			this.setServerCoreRun();

			// 断开所有客户端的连接
			for(Map.Entry<String, NewClient> all : this.allClient.entrySet()) {
				// 结束收发状态
				all.getValue().setServerCoreRun();

				// 关闭客户端套接字
				all.getValue().socket.close();
			}

			// 清空客户端容器内的所有客户端
			this.allClient.clear();

			// 关闭服务器套接字
			this.serverSocket.close();

		} catch (IOException e) {
			e.printStackTrace();
		}
	}


	/**
	 * 扫描所有在线客户端，并设置到表格中。
	 */
	public void scanOnlineClient() {
		// 起始序号
		int index = 1;

		// 每次扫描前将表格中的数据清空
		this.serverView.getDefaultTableModel().setRowCount(0);

		// 获取到所有的在线用户
		for(Map.Entry<String, NewClient> all : this.getAllClient().entrySet()) {

			// 获取到每一个客户端的连接信息
			Object[] info = {index, all.getValue().getClientName(), all.getValue().getClientIP(),
					all.getValue().getClientPort(), all.getValue().getClientConnectDateTime()};

			// 将数据添加到表格中
			this.serverView.getDefaultTableModel().addRow(info);

			// 序号加一
			index += 1;
		}
		// 清空序号
		index = 1;
	}


	/**
	 * 获取到所有在线的客户端的名称
	 *
	 * #1 将所有在线的客户端名称合并为一条报文。
	 * #2 定义报文头：#Z_X_K_H_D#
	 * #3 报文段分隔符：*K_G*
	 * #4 定义报文内容分隔符：$A_N_D$
	 * #5 报文格式为：#Z_X_K_H_D# + *K_G* + 在线客户端名称 + $A_N_D$ + 在线客户端名称 + $A_N_D$
	 * #6 #Z_X_K_H_D#*K_G*在线1$A_N_D$在线2$A_N_D$在线3
	 */
	public String getAllClientName() {
		if(this.allClient.size() > 0) {
			StringBuilder stringBuilder = new StringBuilder();

			//添加报文头 #Z_X_K_H_D#
			stringBuilder.append("#Z_X_K_H_D#");

			//添加报文段分隔符 *K_G*
			stringBuilder.append("*K_G*");

			// 遍历所有在线的客户端
			for(Map.Entry<String, NewClient> all : allClient.entrySet()) {
				// 定义分隔符，将所有在线的客户端名称合并为一条报文，使用 $A_N_D$ 来分割。
				stringBuilder.append(all.getValue().getClientName() + "$A_N_D$");
			}
			return stringBuilder.toString();
		}
		return null;
	}


	/**
	 * 返回服务器运行状态
	 */
	public boolean getServerCoreRun() {
		return isServerCoreRun;
	}


	/**
	 * 返回客户端容器
	 */
	public Map<String, NewClient> getAllClient() {
		return allClient;
	}


	@Override
	public void run() {
		try {
			// 服务器在运行才能让客户端连接
			while (this.isServerCoreRun) {

				// 阻塞式等待客户端连接
				Socket socket = this.serverSocket.accept();

				// 获取连接服务器客户端的名称
				String clientName = new DataInputStream(socket.getInputStream()).readUTF();

				// 获取连接服务器客户端的IP地址
				String clientIP = socket.getInetAddress().getHostAddress();

				// 获取连接服务器客户端的端口号
				int clientPort = socket.getPort();

				// 获取连接服务器客户端的连接时间
				String clientConnectDateTime = ServiceTools.getCurrentDateTime();

				// 创建一个客户端
				NewClient newClient = new NewClient(socket, clientName, clientIP, clientPort, clientConnectDateTime);

				// 将客户端添加到客户端容器中
				this.allClient.put(clientName, newClient);

				// 启动该客户端线程
				new Thread(newClient).start();

				ServiceTools.addSystemNewsRecord(this.serverView.getjTextArea(),
						"客户端 " + clientName + "(" + clientIP + ") 已连接到服务器");

				// 连接成功后向所有在线的客户端发送一条报文，通知它们我上线了。
				newClient.sendAllMsg(MsgTypeEnum.SYSTEM_NOTICE_MSG, clientName, "已加入");

				// 当有新的客户端连接到服务时，刷新在线客户端表格。
				this.scanOnlineClient();

				// 当有新客户端连接到服务器时，向所有在线客户端发送当前在线客户端名称的报文。
				newClient.sendAllMsg(MsgTypeEnum.CLIENT_ONLINE_MSG, null, this.getAllClientName());
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}


	
	/**
	 *  成功连接服务器的客户端
	 */
	class NewClient implements Runnable {

		// 客户端套接字
		private Socket socket;

		// 数据输入流
		private DataInputStream dataInputStream;

		// 数据输出流
		private DataOutputStream dataOutputStream;

		// 客户端运行(收、发报文)状态
		private boolean isNewClientRun = true;

		// 客户端的名称
		private String clientName;

		// 客户端的IP地址
		private String clientIP;

		// 客户端的端口号
		private int clientPort;

		// 客户端的连接时间
		private String clientConnectDateTime;

		public NewClient() {}

		// 构造方法初始化成员属性
		public NewClient(Socket socket, String clientName, String clientIP, int clientPort, String clientConnectDateTime) {
			this.socket = socket;
			this.clientName = clientName;
			this.clientIP = clientIP;
			this.clientPort = clientPort;
			this.clientConnectDateTime = clientConnectDateTime;
			try {
				// 创建客户端数据输入、输出流
				this.dataInputStream = new DataInputStream(this.socket.getInputStream());
				this.dataOutputStream = new DataOutputStream(this.socket.getOutputStream());
			} catch (IOException e) {
				e.printStackTrace();
				// 发生异常关闭就关闭当前客户端
				closeCurrentClient();
			}
		}


		/**
		 * 接收报文(其他客户端发出)
		 */
		public String receiveMsg() throws IOException {
			// 接收报文
			return this.dataInputStream.readUTF();
		}


		/**
		 * 发送报文
		 */
		public void sendMsg(String msg) throws IOException {
			// 发送报文
			this.dataOutputStream.writeUTF(msg);
			// 清空报文缓存
			this.dataOutputStream.flush();
		}


		/**
		 * 发送报文到客户端
		 *
		 * @param msgTypeEnum 报文类型
		 * @param clientName 指定的客户端名称
		 * @param msg 报文内容
		 */
		public void sendAllMsg(MsgTypeEnum msgTypeEnum, String clientName, String msg) throws IOException {

			// 群聊消息添加标志为，避免多次添加消息通知到消息框。
			boolean start = true;

			// 遍历每一个已连接到服务器的客户端
			for(Map.Entry<String, NewClient> all : allClient.entrySet()) {

				// 判断报文级别
				switch (msgTypeEnum) {
					// 系统报文
					case SYSTEM_NOTICE_MSG : {
						// 报文类型 + 通知时间 + 客户端名称 + 报文内容
						all.getValue().sendMsg("[系统通知 " + ServiceTools.getCurrentDateTime() + "]："
								+ clientName + msg);break;
					}

					// 客户端在线状态报文
					case CLIENT_ONLINE_MSG : {
						// 发送报文
						all.getValue().sendMsg(msg);break;
					}

					// 多方通信报文
					case PUBLIC_CHAT_MSG : {
						// 客户端名称 + 发送时间 +  报文内容
						String info = "[" + clientName + " " + ServiceTools.getCurrentDateTime() +  "]：" + msg;

						// 发送报文
						all.getValue().sendMsg(info);

						// 是否已经添加过消息
						if(start) {
							// 添加到消息记录框
							ServiceTools.addAllNewsRecord(serverView, info);

							// 修改添加状态
							start = false;
						}
						break;
					}

					// 单方通信报文
					case PRIVATE_CHAT_MSG : {
						// 报文发送给指定客户端
						if(clientName.equals(all.getKey())) {
							// 客户端名称 + 发送时间 +  报文内容
							String info = "[@" + this.clientName + " " + ServiceTools.getCurrentDateTime()
									+  " 向你发送了一条报文]：" + msg;

							// 发送报文
							all.getValue().sendMsg(info);

							String info2 = "[客户端 @" + this.clientName + " " + ServiceTools.getCurrentDateTime()
									+  " 向客户端 @" + clientName + " 发送了一条报文]：" + msg;

							// 添加到消息记录框
							ServiceTools.addAllNewsRecord(serverView, info2);break;
						}
					}
				}
			}
		}


		/**
		 * 断开当前客户端的连接释放资源
		 */
		public void closeCurrentClient() {
			try {

				// 结束客户端的运行状态
				this.isNewClientRun = false;

				// 断开数据输出出流
				if(this.dataOutputStream != null) {
					this.dataOutputStream.close();
				}

				// 断开数据输入出流
				if(this.dataInputStream != null) {
					this.dataInputStream.close();
				}

				// 断开客户端套解析
				if(this.socket != null) {
					this.socket.close();
				}

				// 将该客户端从客户端容器中删除
				allClient.remove(this.clientName);

				// 向所有客户端发送下线报文
				this.sendAllMsg(MsgTypeEnum.SYSTEM_NOTICE_MSG, this.clientName, "已退出");

				// 添加客户端下线通知
				ServiceTools.addSystemNewsRecord(serverView.getjTextArea(),
						"客户端 " + this.clientName + "(" + this.clientIP + ") 与服务器断开连接");

				// 当有客户端与服务器断开连接时，刷新在线客户端表格。
				scanOnlineClient();

				// 当有客户端断开连接时，向所有在线客户端发送当前在线客户端名称的报文。
				this.sendAllMsg(MsgTypeEnum.CLIENT_ONLINE_MSG, null, getAllClientName());

			} catch (IOException e) {
				e.printStackTrace();
			}
		}


		/**
		 * 解析报文
		 *
		 * #1 解析报文，判断是多方通信还是单方通信。
		 * #2 通信报文头：@D_F_T_X@
		 * #3 报文段分隔符：*K_G*
		 * #4 客户端单方通信报文格式：报文头 + 报文段分隔符 + 私聊的客户端名称 + 报文段分隔符 + 报文内容
		 * #5 @D_F_T_X@*K_G*客户端1*K_G*你好啊！我是客户端1。
		 */
		public void analysisMag(String msg) {
			try{
				// 报文信息必须大于定义的报文头和分隔符
				if(msg.length() > 14) {

					// 判断当前报文是否为私聊
					if("@D_F_T_X@".equals(msg.substring(0, 9))) {

						// 单方通信
						String[] msgData = msg.split("\\*K_G\\*");

						// 单方通信 msgData[1]发送到那个客户端, msgData[2] 发送的内容。
						this.sendAllMsg(MsgTypeEnum.PRIVATE_CHAT_MSG, msgData[1], msgData[2]);

						return;
					}
				}

				// 多方通信
				this.sendAllMsg(MsgTypeEnum.PUBLIC_CHAT_MSG, this.clientName, msg);

			}catch (IOException e) {
				e.printStackTrace();
			}
		}


		/**
		 * 设置服务器内客户端的运行状态
		 *
		 * #1 停止服务器内客户端的运行，客户端即无法收、发报文。
		 */
		public synchronized  void setServerCoreRun() {
			this.isNewClientRun = false;
		}


		/**
		 * 返回客户端名称
		 */
		public String getClientName() {
			return clientName;
		}

		/**
		 * 返回客户端IP地址
		 */
		public String getClientIP() {
			return clientIP;
		}


		/**
		 * 返回客户端端口号
		 */
		public int getClientPort() {
			return clientPort;
		}


		/**
		 * 返回客户端的连接时间
		 */
		public String getClientConnectDateTime() {
			return clientConnectDateTime;
		}


		@Override
		public void run() {
			try {
				// 客户端在运行才能收发报文
				while (this.isNewClientRun) {
					// 获取到客户端发送的报文
					String msg = this.receiveMsg();

					// 发送的报文不为null
					if(!"".equals(msg) && msg != null) {
						// 解析报文类型
						this.analysisMag(msg);
					}
				}
			} catch (IOException e) {
				e.printStackTrace();
				// 发生异常关闭就关闭当前客户端
				closeCurrentClient();
			}
		}
	}

}